package com.zhuhjay.service.impl;

import com.zhuhjay.domain.FileChunk;
import com.zhuhjay.domain.FileDetails;
import com.zhuhjay.domain.dto.FileChunkDto;
import com.zhuhjay.domain.http.HttpCode;
import com.zhuhjay.domain.http.Result;
import com.zhuhjay.domain.vo.CheckChunkVo;
import com.zhuhjay.service.FileChunkService;
import com.zhuhjay.service.FileDetailsService;
import com.zhuhjay.service.FileStorageService;
import com.zhuhjay.utils.BeanBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;

/**
 * @author ZhuHJay
 * @date 2023/1/5 22:14
 */
@Slf4j
@Service
public class FileStorageServiceImpl implements FileStorageService {

    private final FileChunkService chunkService;
    private final FileDetailsService detailsService;

    @Value("${file.storage-path}")
    private String storagePath;

    public FileStorageServiceImpl(FileChunkService chunkService, FileDetailsService detailsService) {
        this.chunkService = chunkService;
        this.detailsService = detailsService;
    }

    @Override
    public CheckChunkVo check(FileChunkDto chunkInfo) {
        Long count = detailsService.lambdaQuery()
                .eq(FileDetails::getIdentifier, chunkInfo.getIdentifier())
                .count();
        // 文件已上传
        if (count != null && count > 0) {
            return BeanBuilder.builder(CheckChunkVo::new)
                    .with(CheckChunkVo::setUploaded, true)
                    .build();
        }
        List<FileChunk> list = chunkService.lambdaQuery()
                .eq(FileChunk::getIdentifier, chunkInfo.getIdentifier())
                .orderByAsc(FileChunk::getChunkNum).list();
        // 文件未上传
        if (list.size() == 0) {
            return BeanBuilder.builder(CheckChunkVo::new)
                    .with(CheckChunkVo::setUploaded, false)
                    .build();
        }
        // 返回已上传的分片
        List<Integer> chunkList = new ArrayList<>();
        list.forEach(chunk -> chunkList.add(chunk.getChunkNum()));
        return BeanBuilder.builder(CheckChunkVo::new)
                .with(CheckChunkVo::setUploaded, false)
                .with(CheckChunkVo::setUploadedChunks, chunkList)
                .build();
    }

    @Override
    public Result upload(FileChunkDto chunkInfo) {
        MultipartFile multipartFile = chunkInfo.getFile();
        if (multipartFile == null) {
            return Result.fail("禁止文件空上传");
        }
        // 单分片文件
        String fileName = storagePath + "/" + chunkInfo.getIdentifier();
        if (chunkInfo.getTotalChunks() == 1) {
            File file = new File(fileName);
            try {
                multipartFile.transferTo(file);
                detailsService.save(BeanBuilder.builder(FileDetails::new)
                        .with(FileDetails::setRealName, multipartFile.getOriginalFilename())
                        .with(FileDetails::setIdentifier, chunkInfo.getIdentifier())
                        .with(FileDetails::setType, multipartFile.getContentType())
                        .with(FileDetails::setSize, chunkInfo.getTotalSize())
                        .build());
                return Result.ok();
            } catch (IOException e) {
                e.printStackTrace();
                return Result.fail(HttpCode.SERVER_ERROR, "文件上传失败");
            }
        } else {
            return shardingUploadFile(chunkInfo);
        }

    }

    private Result shardingUploadFile(FileChunkDto chunkInfo) {
        MultipartFile multipartFile = chunkInfo.getFile();
        String fileName = storagePath + "/" + chunkInfo.getIdentifier();
        try (RandomAccessFile file = new RandomAccessFile(fileName, "rw")) {
            long chunkSize = chunkInfo.getChunkSize();
            long offset = (chunkInfo.getChunkNumber() - 1) * chunkSize;
            file.seek(offset);
            file.write(multipartFile.getBytes());
            log.warn("{} write ok! total {}", chunkInfo.getChunkNumber(), chunkInfo.getTotalChunks());
        } catch (Exception e) {
            e.printStackTrace();
            return Result.fail(HttpCode.SERVER_ERROR, "文件上传失败");
        }

        // 保存分片信息
        FileChunk fileChunk = new FileChunk();
        fileChunk.setIdentifier(chunkInfo.getIdentifier());
        fileChunk.setChunkNum(chunkInfo.getChunkNumber());
        chunkService.save(fileChunk);

        Long count = chunkService.lambdaQuery()
                .eq(FileChunk::getIdentifier, chunkInfo.getIdentifier())
                .count();

        /* TODO 经过测试，每个分片都会按顺序进行上传
            也就是说可以判断上传是否为最后一个分片来判断是否上传完成
            这里用了 synchronized 是认为说如果并发上传可能会导致多线程问题
            对前端上传框架还不是很熟悉，需要进一步了解才能直到需不需要加锁
         */
        // 如果分片都还没有上传齐
        if (count == null || count.intValue() != chunkInfo.getTotalChunks()) {
            return Result.ok();
        }

        // 到这里说明文件上传成功了
        synchronized (chunkService) {
            // 判断是否已经存储了信息
            Long exit = detailsService.lambdaQuery()
                    .eq(FileDetails::getIdentifier, chunkInfo.getIdentifier())
                    .count();
            // 没有存储过才进行存储，并删除分片信息
            if (exit != null && exit.intValue() == 0) {
                detailsService.save(BeanBuilder.builder(FileDetails::new)
                        .with(FileDetails::setRealName, multipartFile.getOriginalFilename())
                        .with(FileDetails::setIdentifier, chunkInfo.getIdentifier())
                        .with(FileDetails::setType, multipartFile.getContentType())
                        .with(FileDetails::setSize, chunkInfo.getTotalSize())
                        .build());
                // 删除所有分片
                chunkService.lambdaUpdate()
                        .eq(FileChunk::getIdentifier, chunkInfo.getIdentifier())
                        .remove();
            }
        }

        return Result.ok();
    }
}
